/* 
 * 
 * Copyright 2011 The University of Adelaide
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License. 
 *  
 */

package au.org.aekos.ands.global;

import java.util.Date;
import java.util.Random;

import org.apache.xmlbeans.XmlOptions;

import au.org.aekos.ands.global.Schema.Attribute;
import au.org.ands.standards.rifCs.registryObjects.DescriptionType;
import au.org.ands.standards.rifCs.registryObjects.NameType;
import au.org.ands.standards.rifCs.registryObjects.RegistryObjectsDocument;
import au.org.ands.standards.rifCs.registryObjects.RegistryObjectsDocument.RegistryObjects.RegistryObject;

/**
 * Utility class for various bits of functionality used in the course of producing <a href="http://ands.org.au/resource/rif-cs.html">RIF-CS</a>.  This functionality includes:
 * 
 * <ul>
 * <li>Equivalents of <code>Math.{min,max}</code> for {@link Date};
 * <li>Anonymising and pluralising strings; and
 * <li>Performing operations on arrays of name and description elements.
 * </ul>
 */
public class Util {

	private Util() {} 
	
	/**
	 * Returns the earlier date out of the two parameters.
	 *
	 * @param d1 a date.
	 * @param d2 another date.
	 * @return the earlier date of the two.
	 */
	public static Date earliest(Date d1, Date d2) {
		if (d1 == null) { return d2; }
		if (d2 == null) { return d1; }

		if (d1.after(d2)) {
			return d2;
		} else {
			return d1;
		}
	}

	/**
	 * Returns the later date out of the two parameters.
	 *
	 * @param d1 a date.
	 * @param d2 another date.
	 * @return the later date of the two.
	 */
	public static Date latest(Date d1, Date d2) {
		if (d1 == null) { return d2; }
		if (d2 == null) { return d1; }

		if (d1.after(d2)) {
			return d1;
		} else {
			return d2;
		}
	}

	private static final char[] alpha = "abcdefghijklmnopqrstuvwxyz".toCharArray();
	private static final char[] numeric = "0123456789".toCharArray();
	private static final Random random = new Random();

	/**
	 * Generates an anonymised string using like-for-like characters. That is, lower- and upper-case
	 * alphabetical and numeric characters are replaced by random characters of the same type.
	 * Non-alphanumeric characters, such as punctuation, are passed through to the resultant
	 * string unmodified.
	 *
	 * @param s the string to be anonymised.
	 * @return the anonymised string.
	 */
	public static String anon(String s) {
		if ((s == null) || s.isEmpty()) { return ""; }

		char[] array = s.toCharArray();
		for (int i = 0; i < array.length; i++) {
			if (Character.isLowerCase(array[i])) {
				array[i] = alpha[random.nextInt(alpha.length)];
			} else if (Character.isUpperCase(array[i])) {
				array[i] = Character.toUpperCase(alpha[random.nextInt(alpha.length)]);
			} else if (Character.isDigit(array[i])) {
				array[i] = numeric[random.nextInt(numeric.length)];
			}
		}
		return new String(array);
	}

	/**
	 * Pluralises a noun based on whether its count is greater than one.
	 * The default suffix of "s" can optionally
	 * be replaced with "es" through the use of a boolean parameter. For example,
	 * 
	 * <p>
	 * <code>pluralise(8, "apple", false)</code>
	 * &rarr;
	 * <code>"8 apples"</code>
	 * 
	 * <p>
	 * <code>pluralise(4, "potato", true)</code>
	 * &rarr;
	 * <code>"4 potatoes"</code>
	 *
	 * @param num the item count.
	 * @param what the item name.
	 * @param withE whether the suffix should be "es" instead of "s".
	 * @return the resultant string.
	 */
	public static String pluralise(int num, String what, boolean withE) {
		return num + " " + what + ((num > 1) ? (withE ? "es" : "s") : "");
	}

	/**
	 * Checks whether a name array contains an entry of the given type.
	 *
	 * @param array the name array.
	 * @param type the type.
	 * @return true, if an element of the given type is present, else false.
	 */
	public static boolean hasName(NameType[] array, Attribute type) {
		return (getName(array, type) != null);
	}
	
	/**
	 * Returns the first name of the given type within the array, if one is present.
	 *
	 * @param array the name array.
	 * @param type the type.
	 * @return the name, if present, else null.
	 */
	public static NameType getName(NameType[] array, Attribute type) {
		for (NameType name : array) {
			if (name.getType().equalsIgnoreCase(type.toString())) {
				return name;
			}
		}
		return null;
	}
	
	/**
	 * Checks whether a description array contains an entry of the given type.
	 *
	 * @param array the description array.
	 * @param type the type.
	 * @return true, if an element of the given type is present, else false.
	 */
	public static boolean hasDescription(DescriptionType[] array, Attribute type) {
		return (getDescription(array, type) != null);
	}
	
	/**
	 * Returns the first description of the given type within the array, if one is present.
	 *
	 * @param array the description array.
	 * @param type the type.
	 * @return the description, if present, else null.
	 */
	public static DescriptionType getDescription(DescriptionType[] array, Attribute type) {
		for (DescriptionType description : array) {
			if (description.getType().equalsIgnoreCase(type.toString())) {
				return description;
			}
		}
		return null;
	}
	
	/** The Constant XML_MAGIC. */
	private static final String XML_MAGIC = "<?xml version=\"1.0\"?>";
	
	/** The Constant XML_OPTIONS. */
	private static final XmlOptions XML_OPTIONS = new XmlOptions().setSavePrettyPrint().setSaveAggressiveNamespaces().setSaveOuter();
	
	/**
	 * Serialise a RIF-CS <a href="http://ands.org.au/guides/cpguide/cpgrecstructure.html">Registry Object</a> to a XML string.
	 *
	 * @param object the object.
	 * @return the string.
	 */
	public static String serialise(RegistryObject object) {
		if (object == null) { return null; }
    	RegistryObjectsDocument doc = RegistryObjectsDocument.Factory.newInstance();
		doc.addNewRegistryObjects().addNewRegistryObject().set(object);
		return XML_MAGIC + doc.xmlText(XML_OPTIONS);
	}
}
